package za.org.jcicada.syshealth;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Date;
import java.util.List;
import java.util.Observable;

import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYStepRenderer;
import org.jfree.data.time.Millisecond;
import org.jfree.data.time.RegularTimePeriod;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;

import za.org.jcicada.datastore.impl.DefaultDataStoreClient;
import za.org.jcicada.genss.PhysicalURN;
import za.org.jcicada.genss.ServiceURN;
import za.org.jcicada.genss.SystemURN;
import za.org.jcicada.genss.messages.Result;
import za.org.jcicada.genss.messages.SuccessCode;
import za.org.jcicada.genss.sensor.ISensor;
import za.org.jcicada.genss.sensor.ISensorValue;
import za.org.jcicada.genss.sensor.SensorURN;
import za.org.jcicada.genss.sensor.impl.SensorStatusValue;
import za.org.jcicada.genss.sensor.impl.TypeDiscrete;
import za.org.jcicada.genss.sensor.impl.TypeInt;

/**
 * The user interface for the system health display. It does not extend View
 * directly but rather contains a View object because it is already extending
 * JFrame. Responsible for publishing the view and interacting with the user.
 * Queries the model and receives control from the controller.
 * 
 * @author Robert Crida
 */
public class SystemHealthUI extends javax.swing.JFrame implements View {
	private static final long serialVersionUID = 1L;

	/** MVC Model handle */
	private Model model;

	/** MVC Controller handle */
	private Controller controller;

	/** Selected sensor */
	private ISensor sensor;

	private TimeSeries series;
	
	private TimeSeries statusSeries;
	
	private ChartPanel chartPanel;
	
	/**
	 * Constructs the view component of the Model-View-Controller architecture.
	 * Configures the logger, initializes graphical components and constructs
	 * the IU controller using the communications elements required. Sets the
	 * initial state of the System Health Display and initializes the UI
	 * controller.
	 */
	public SystemHealthUI(Model model) {
		this.model = model;
		initComponents();
		controller = null;
		sensor = null;
		model.addObserver(this);
	}

	public Controller getController() {
		return controller;
	}

	public Model getModel() {
		return model;
	}

	public void initialize() {
		controller = makeController();
	}

	public Controller makeController() {
		return new ControllerBase(this);
	}

	public void update(Observable o, Object arg) {
		if (sensor != null && sensor.equals(o)) {
			jLabelValue.setText(String.format("%s", sensor.getValue().getValue()));
			jLabelStatus.setText(sensor.getStatusValue().getStatus().toString());
			jLabelStatus.setForeground(SensorColourMap.getSensorColor(sensor));
			RegularTimePeriod period = new Millisecond(new Date(sensor.getValue().getTime()));
			if (arg instanceof ISensorValue) {
				if (series != null) {
					double yval = sensor.getType().doubleValueOf(sensor.getValue().getValue());
					series.addOrUpdate(period, yval);
				}
			} else {
				if (statusSeries != null) {
					statusSeries.addOrUpdate(period, sensor.getStatusValue().getStatus().ordinal());
				}
			}
		}
		jListSystem.validate();
		jListSensor.validate();
	}
	
	/**
	 * This method is called from within the constructor to initialize the form.
	 * WARNING: Do NOT modify this code. The content of this method is always
	 * regenerated by the Form Editor.
	 */
	// <editor-fold defaultstate="collapsed"
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        jPanelLayout = new javax.swing.JPanel();
        jPanelPhysicalLayout = new javax.swing.JPanel();
        jScrollPanePhysical = new javax.swing.JScrollPane();
        jTreePhysical = new javax.swing.JTree();
        jComboBoxView = new javax.swing.JComboBox();
        jScrollPaneSystem = new javax.swing.JScrollPane();
        jListSystem = new javax.swing.JList();
        jScrollPaneService = new javax.swing.JScrollPane();
        jListService = new javax.swing.JList();
        jScrollPaneSensor = new javax.swing.JScrollPane();
        jListSensor = new javax.swing.JList();
        jPanelDetails = new javax.swing.JPanel();
        jLabel1 = new javax.swing.JLabel();
        jLabel2 = new javax.swing.JLabel();
        jLabel3 = new javax.swing.JLabel();
        jLabel4 = new javax.swing.JLabel();
        jLabel5 = new javax.swing.JLabel();
        jLabel6 = new javax.swing.JLabel();
        jLabelName = new javax.swing.JLabel();
        jLabelDescription = new javax.swing.JLabel();
        jLabelUnits = new javax.swing.JLabel();
        jLabelValue = new javax.swing.JLabel();
        jLabelStatus = new javax.swing.JLabel();
        jLabelLocation = new javax.swing.JLabel();
        jButtonHalt = new javax.swing.JButton();
        jButtonRestart = new javax.swing.JButton();
        
        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                formWindowClosing(evt);
            }
        });

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.gridx = 0;

        jPanelLayout.setLayout(new java.awt.GridBagLayout());
        jPanelPhysicalLayout.setLayout(new java.awt.GridBagLayout());
        jComboBoxView.setModel(model.getViewModel());
        jComboBoxView.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				jComboBoxViewChanged(arg0);
			}
        });
        jPanelPhysicalLayout.add(jComboBoxView, gridBagConstraints);
        jPanelPhysicalLayout.add(jScrollPanePhysical, gridBagConstraints);
        gridBagConstraints.gridx = java.awt.GridBagConstraints.RELATIVE;
        jPanelLayout.add(jPanelPhysicalLayout, gridBagConstraints);
        jScrollPanePhysical.setViewportView(jTreePhysical);
        jTreePhysical.setModel(model.getPhysicalModel());
        jTreePhysical.getSelectionModel().setSelectionMode(javax.swing.tree.TreeSelectionModel.SINGLE_TREE_SELECTION);
        jTreePhysical.addTreeSelectionListener(new TreeSelectionListener() {
			public void valueChanged(TreeSelectionEvent e) {
				jTreePhysicalValueChanged(e);
			}
        });
        jTreePhysical.setShowsRootHandles(true);
        jTreePhysical.getModel().addTreeModelListener(new TreeModelListener() {
			public void treeNodesChanged(TreeModelEvent arg0) {
			}
			public void treeNodesInserted(TreeModelEvent arg0) {
				jTreePhysical.expandPath(arg0.getTreePath());
				pack();
			}
			public void treeNodesRemoved(TreeModelEvent arg0) {
			}
			public void treeStructureChanged(TreeModelEvent arg0) {
			}
        });
        jTreePhysical.setCellRenderer(new PhysicalTreeCellRenderer(model.getPhysicalModel()));

        
        jListSystem.setModel(model.getSystemModel());
        jListSystem.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
        jListSystem.addListSelectionListener(new javax.swing.event.ListSelectionListener() {
            public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
                jListSystemValueChanged(evt);
            }
        });
        jPanelLayout.add(jScrollPaneSystem, gridBagConstraints);
        jScrollPaneSystem.setViewportView(jListSystem);

        
        jListService.setModel(model.getServiceModel());
        jListService.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
        jListService.addListSelectionListener(new javax.swing.event.ListSelectionListener() {
            public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
                jListServiceValueChanged(evt);
            }
        });
        jPanelLayout.add(jScrollPaneService, gridBagConstraints);
        jScrollPaneService.setViewportView(jListService);

        
        jListSensor.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
        jListSensor.addListSelectionListener(new javax.swing.event.ListSelectionListener() {
            public void valueChanged(javax.swing.event.ListSelectionEvent evt) {
                jListSensorValueChanged(evt);
            }
        });
        jListSensor.setCellRenderer(new ColourListCellRenderer());
        jPanelLayout.add(jScrollPaneSensor, gridBagConstraints);
        jScrollPaneSensor.setViewportView(jListSensor);

        
        jPanelLayout.add(jPanelDetails, gridBagConstraints);
        jPanelDetails.setLayout(new java.awt.GridBagLayout());

        jLabel1.setText("Name:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.ipadx = 10;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        jPanelDetails.add(jLabel1, gridBagConstraints);

        jLabel2.setText("Description:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.ipadx = 10;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        jPanelDetails.add(jLabel2, gridBagConstraints);

        jLabel3.setText("Units:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.ipadx = 10;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        jPanelDetails.add(jLabel3, gridBagConstraints);

        jLabel4.setText("Value:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.ipadx = 10;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        jPanelDetails.add(jLabel4, gridBagConstraints);

        jLabel5.setText("Status:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.ipadx = 10;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        jPanelDetails.add(jLabel5, gridBagConstraints);
        
        jLabel6.setText("Location:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.ipadx = 10;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        jPanelDetails.add(jLabel6, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        jPanelDetails.add(jLabelName, gridBagConstraints);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        jPanelDetails.add(jLabelDescription, gridBagConstraints);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        jPanelDetails.add(jLabelUnits, gridBagConstraints);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        jPanelDetails.add(jLabelValue, gridBagConstraints);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        jPanelDetails.add(jLabelStatus, gridBagConstraints);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        jPanelDetails.add(jLabelLocation, gridBagConstraints);

        jButtonHalt.setText("Halt");
        jButtonHalt.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonHaltActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        jPanelDetails.add(jButtonHalt, gridBagConstraints);

        jButtonRestart.setText("Restart");
        jButtonRestart.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonRestartActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        jPanelDetails.add(jButtonRestart, gridBagConstraints);


        getContentPane().add(jPanelLayout, java.awt.BorderLayout.CENTER);
        pack();
    }// </editor-fold>//GEN-END:initComponents

	private void jComboBoxViewChanged(ActionEvent arg0) {
		model.getPhysicalModel().setView(jComboBoxView.getSelectedItem().toString());
		
	}
	private void jTreePhysicalValueChanged(TreeSelectionEvent evt) {
		TreePath leadPath = evt.getNewLeadSelectionPath();
		DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) leadPath.getLastPathComponent();
		PhysicalURN physicalURN = (PhysicalURN) treeNode.getUserObject();
		model.selectPhysicalNode(physicalURN, treeNode.isLeaf());
		SensorURN sensorURN = model.getPhysicalModel().getSensorURN(physicalURN);
		ISensor selectedSensor = model.getPhysicalModel().getSensor(physicalURN);
		selectSensor(sensorURN, selectedSensor);
	}

	private void jListSystemValueChanged(javax.swing.event.ListSelectionEvent evt) {// GEN-FIRST:event_jList1ValueChanged
		if (!evt.getValueIsAdjusting()) {
			SystemURN systemURN = (SystemURN) jListSystem.getSelectedValue();
			jListSensor.setModel(model.getSensorModel(systemURN));
		}
	}// GEN-LAST:event_jList1ValueChanged

	private void jListSensorValueChanged(javax.swing.event.ListSelectionEvent evt) {// GEN-FIRST:event_jList2ValueChanged
		if (!evt.getValueIsAdjusting()) {
			ISensor selectedSensor = (ISensor) jListSensor.getSelectedValue();
			if (selectedSensor != null) {
				SystemURN systemURN = (SystemURN) jListSystem.getSelectedValue();
				try {
					SensorURN sensorURN = new SensorURN(systemURN, selectedSensor.getName());
					selectSensor(sensorURN, selectedSensor);
				} catch (URISyntaxException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}// GEN-LAST:event_jList2ValueChanged

	private void selectSensor(SensorURN sensorURN, ISensor selectedSensor) {
		if (sensor != null) {
			sensor.deleteObserver(this);
		}
		if (chartPanel != null) {
			jPanelDetails.remove(chartPanel);
			chartPanel = null;
		}
		sensor = selectedSensor;
		if (sensor != null) {
			sensor.addObserver(this);
			jLabelName.setText(sensor.getName());
			jLabelDescription.setText(sensor.getDescription());
			jLabelUnits.setText(sensor.getUnits());
			java.awt.GridBagConstraints gridBagConstraints = new java.awt.GridBagConstraints();
			gridBagConstraints.gridx = 0;
			gridBagConstraints.gridwidth = 2;
			gridBagConstraints.weighty = 1.0;
			gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
			gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
			
			series = new TimeSeries(sensor.getDescription());
			TimeSeriesCollection dataset = new TimeSeriesCollection(series);
			statusSeries = new TimeSeries("Status");
			TimeSeriesCollection statusDataset = new TimeSeriesCollection(statusSeries);
			JFreeChart chart = ChartFactory.createTimeSeriesChart(
					sensor.getDescription(), 
					"Time", 
					sensor.getUnits(),
					dataset, 
					false, 
					true, 
					false
			);
			final XYPlot plot = chart.getXYPlot();
			plot.setDataset(1, statusDataset);
			XYItemRenderer renderer = new StandardXYItemRenderer();
			if (sensor.getType() instanceof TypeDiscrete || sensor.getType() instanceof TypeInt) {
				renderer = new XYStepRenderer();
			}
			plot.setRenderers(new XYItemRenderer[]{renderer, new TimeStatusRenderer()});
			ValueAxis axis = plot.getDomainAxis();
			axis.setAutoRange(true);
			long duration_ms = 3000000;
			axis.setFixedAutoRange(duration_ms);  // 60 seconds
			axis = plot.getRangeAxis();
			if (sensor.getType() instanceof TypeDiscrete) {
				NumberAxis numberAxis = new NumberAxis();
				TypeDiscrete type = (TypeDiscrete) sensor.getType();
				numberAxis.setTickUnit(new DiscreteTickUnit(type));
				numberAxis.setRange(0, ((TypeDiscrete)sensor.getType()).toStringArray().length);
				plot.setRangeAxis(numberAxis);
			}
			chartPanel = new ChartPanel(chart);
			jPanelDetails.add(chartPanel, gridBagConstraints);
			pack();
			
			long currentTimeInMillis = System.currentTimeMillis();
			model.getDataStoreServer().requestSensorHistory(dataStoreClient, sensorURN, new Date(currentTimeInMillis - duration_ms), new Date(currentTimeInMillis));
			model.getDataStoreServer().requestStatusHistory(dataStoreClient, sensorURN, new Date(currentTimeInMillis - duration_ms), new Date(currentTimeInMillis));
			// make sure its current value is displayed
			update((Observable) sensor, sensor.getValue());
		}
	}
	
	private void jListServiceValueChanged(javax.swing.event.ListSelectionEvent evt) {// GEN-FIRST:event_jList3ValueChanged
		if (!evt.getValueIsAdjusting()) {
			ServiceURN serviceURN = (ServiceURN) jListService.getSelectedValue();
			jLabelLocation.setText(model.getServiceLocation(serviceURN));
		}
	}// GEN-LAST:event_jList3ValueChanged

	private void jButtonHaltActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_jButtonHaltActionPerformed
		model.halt((SystemURN) jListSystem.getSelectedValue());
	}// GEN-LAST:event_jButtonHaltActionPerformed

	private void jButtonRestartActionPerformed(java.awt.event.ActionEvent evt) {// GEN-FIRST:event_jButtonRestartActionPerformed
		model.restart((SystemURN) jListSystem.getSelectedValue());
	}// GEN-LAST:event_jButtonRestartActionPerformed

	/**
	 * Ensures the UI controller is shut down when the view closes.
	 * 
	 * @param evt
	 */
	private void formWindowClosing(java.awt.event.WindowEvent evt) {// GEN-FIRST:event_formWindowClosing
	/*
	 * try { getModel().shutdown();// GEN-LAST:event_formWindowClosing } catch
	 * (CommsException ex) { logger.error(null, ex); }
	 */

	}

	/**
	 * Runs the System Health Display
	 * 
	 * @param args
	 *            the command line arguments
	 */
	public static void main(final String args[]) throws URISyntaxException {
		Logger.getRootLogger().setLevel(Level.INFO);
		final Model model = new Model(new URI(args[0]));
		final SystemHealthUI ui = new SystemHealthUI(model);
		java.awt.EventQueue.invokeLater(new Runnable() {
			public void run() {
				ui.initialize();
				ui.update(model, null);
				ui.setVisible(true);
			}
		});
	}

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton jButtonHalt;
    private javax.swing.JButton jButtonRestart;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JLabel jLabel4;
    private javax.swing.JLabel jLabel5;
    private javax.swing.JLabel jLabel6;
    private javax.swing.JLabel jLabelDescription;
    private javax.swing.JLabel jLabelName;
    private javax.swing.JLabel jLabelStatus;
    private javax.swing.JLabel jLabelUnits;
    private javax.swing.JLabel jLabelValue;
    private javax.swing.JLabel jLabelLocation;
    private javax.swing.JList jListSystem;
    private javax.swing.JList jListSensor;
    private javax.swing.JList jListService;
    private javax.swing.JPanel jPanelLayout;
    private javax.swing.JPanel jPanelPhysicalLayout;
    private javax.swing.JPanel jPanelDetails;
    private javax.swing.JScrollPane jScrollPanePhysical;
    private javax.swing.JScrollPane jScrollPaneService;
    private javax.swing.JScrollPane jScrollPaneSystem;
    private javax.swing.JScrollPane jScrollPaneSensor;
    private javax.swing.JTree jTreePhysical;
    private javax.swing.JComboBox jComboBoxView;
    // End of variables declaration//GEN-END:variables
    
    private class DataStoreClient extends DefaultDataStoreClient {

		public void replySensorHistory(Result result, SensorURN sensorURN, Date from, Date to, List<ISensorValue> values) {
			if (result.getCode() == SuccessCode.COMPLETE_SUCCESS) {
				Logger.getLogger(SystemHealthUI.class).info(values.size());
				series.setNotify(false);
				for (ISensorValue value : values) {
					RegularTimePeriod period = new Millisecond(new Date(value.getTime()));
					double yval = sensor.getType().doubleValueOf(value.getValue());
					series.addOrUpdate(period, yval);
				}
				series.setNotify(true);
				series.fireSeriesChanged();
			}
		}

		public void replyStatusHistory(Result result, SensorURN sensorURN, Date from, Date to, List<SensorStatusValue> values) {
			if (result.getCode() == SuccessCode.COMPLETE_SUCCESS) {
				Logger.getLogger(SystemHealthUI.class).info(values.size());
				series.setNotify(false);
				for (SensorStatusValue value : values) {
					RegularTimePeriod period = new Millisecond(new Date(value.getTime()));
					statusSeries.addOrUpdate(period, value.getStatus().ordinal());
				}
				series.setNotify(true);
				series.fireSeriesChanged();
			}
		}
	}
    
    private DataStoreClient dataStoreClient = new DataStoreClient();

}
